﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO.Ports;
using System.Runtime.Serialization;
using System.Text;
using Pahan.McuFusion.Common;
using Pahan.McuFusion.Common.ComponentModel;

namespace Pahan.McuFusion.Spi
{
    [Serializable]
    public class AlgorithmBuilderSpiInterface : ISpiInterface, ISerializable
    {
        #region Constructors
        
        internal AlgorithmBuilderSpiInterface(AlgorithmBuilderSpiInterfaceInfo info)
        {
            this.Info = info;
            port = new SerialPort();
            Init(port.PortName, defaultBaudRate);
        }

        protected AlgorithmBuilderSpiInterface(SerializationInfo info, StreamingContext context)
        {
            port = new SerialPort();
            Init(info.GetString(portNameKey), info.GetInt32(baudRateKey));
            Info = (AlgorithmBuilderSpiInterfaceInfo)info.GetValue(infoKey,
                typeof(AlgorithmBuilderSpiInterfaceInfo));
            InvertMiso = info.GetBoolean(invertMisoKey);
            InvertMosi = info.GetBoolean(invertMosiKey);
            InvertReset = info.GetBoolean(invertResetKey);
            InvertSck = info.GetBoolean(invertSckKey);
            OutputPins = (OutputPinMapping)info.GetValue(outputPinsKey, typeof(OutputPinMapping));
        }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            if (port != null)
            {
                port.Dispose();
            }
        }

        #endregion

        #region Implementation of IHardwareInterface

        /// <summary>
        /// Gets the information about the hardware interface.
        /// </summary>
        public HardwareInterfaceInfo Info
        {
            get;
            private set;
        }

        /// <summary>
        /// Opens the hardware interface.
        /// </summary>
        public void Open()
        {
            port.Open();
        }

        /// <summary>
        /// Closes the hardware interface.
        /// </summary>
        public void Close()
        {
            port.Close();
        }

        #endregion

        #region Implementation of ISpiInterface

        /// <summary>
        /// Gets or sets the SCK signal.
        /// </summary>
        [Browsable(false)]
        public bool Sck
        {
            get { return getSck() ^ InvertSck; }
            set { setSck(value ^ InvertSck); }
        }

        /// <summary>
        /// Gets the MISO signal.
        /// </summary>
        [Browsable(false)]
        public bool Miso
        {
            get { return port.CtsHolding ^ InvertMiso; }
        }

        /// <summary>
        /// Gets or sets MOSI signal.
        /// </summary>
        [Browsable(false)]
        public bool Mosi
        {
            get { return mosi ^ InvertMosi; }
            set
            {
                mosi = value ^ InvertMosi;
                if (mosi)
                {
                    port.BaseStream.WriteByte(0);
                    port.BaseStream.Flush();
                }
            }
        }

        /// <summary>
        /// Gets or sets RESET signal.
        /// </summary>
        [Browsable(false)]
        public bool Reset
        {
            get { return !getReset() ^ InvertReset; }
            set { setReset(!value ^ InvertReset); }
        }

        /// <summary>
        /// Gets or sets the value indicating whether the SCK signal must be inverted.
        /// </summary>
        [DefaultValue(false)]
        [DisplayName("Invert SCK")]
        public bool InvertSck
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the value indicating whether the MISO signal must be inverted.
        /// </summary>
        [DefaultValue(false)]
        [DisplayName("Invert MISO")]
        public bool InvertMiso
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the value indicating whether the MOSI signal must be inverted.
        /// </summary>
        [DefaultValue(false)]
        [DisplayName("Invert MOSI")]
        public bool InvertMosi
        {
            get; set;
        }

        /// <summary>
        /// Gets or sets the value indicating whether the RESET signal must be inverted.
        /// </summary>
        [DefaultValue(false)]
        [DisplayName("Invert RESET")]
        public bool InvertReset
        {
            get; set;
        }

        #endregion

        #region Public Properties

        [TypeConverter(typeof(SerialPortNameTypeConverter))]
        [Description("The name of a COM port to use.")]
        public String Port
        {
            get
            {
                return port.PortName;
            }
            set
            {
                port.PortName = value;
            }
        }

        [DefaultValue(defaultBaudRate)]
        [Description("Baud Rate.")]
        public int BaudRate
        {
            get
            {
                return port.BaudRate;
            }
            set
            {
                port.BaudRate = value;
            }
        }

        [DefaultValue(OutputPinMapping.RtsSck_DtrReset)]
        [Description("Output Pins Mapping.")]
        [DisplayName("Output Pins Mapping")]
        public OutputPinMapping OutputPins
        {
            get
            {
                return outputPins;
            }
            set
            {
                if (outputPins != value)
                {
                    outputPins = value;
                    switch (value)
                    {
                        case OutputPinMapping.RtsSck_DtrReset:
                            getSck = GetRts;
                            setSck = SetRts;
                            getReset = GetDtr;
                            setReset = SetDtr;
                            break;
                        case OutputPinMapping.RtsReset_DtrSck:
                            getSck = GetDtr;
                            setSck = SetDtr;
                            getReset = GetRts;
                            setReset = SetRts;
                            break;
                        default:
                            throw new ArgumentException("The argument has invalid value.");
                    }
                }
            }
        }

        #endregion

        #region Private Members

        private void Init(String portName, int baudRate)
        {
            Port = portName;
            BaudRate = baudRate;
            port.DataBits = 8;
            port.Parity = Parity.None;
            port.StopBits = StopBits.One;
            
            getSck = GetRts;
            setSck = SetRts;
            getReset = GetDtr;
            setReset = SetDtr;
        }

        private bool GetRts()
        {
            return port.RtsEnable;
        }
        
        private void SetRts(bool value)
        {
            port.RtsEnable = value;
        }

        private bool GetDtr()
        {
            return port.DtrEnable;
        }

        private void SetDtr(bool value)
        {
            port.DtrEnable = value;
        }

        private Func<bool> getReset;
        private Action<bool> setReset;

        private Func<bool> getSck;
        private Action<bool> setSck;

        private OutputPinMapping outputPins;

        private readonly SerialPort port;

        private bool mosi;

        private const int defaultBaudRate = 57600;

        private const String portNameKey = "PortName";

        private const String baudRateKey = "BaudRate";

        private const String infoKey = "Info";

        private const String invertMosiKey = "InvertMosi";
        private const String invertMisoKey = "InvertMiso";
        private const String invertSckKey = "InvertSck";
        private const String invertResetKey = "InvertReset";

        private const String outputPinsKey = "OutputPins";

        #endregion

        #region Implementation of ISerializable

        /// <summary>
        /// Populates a <see cref="T:System.Runtime.Serialization.SerializationInfo"/> with the data needed to serialize the target object.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"/> to populate with data. 
        /// </param>
        /// <param name="context">The destination (see <see cref="T:System.Runtime.Serialization.StreamingContext"/>) for this serialization. 
        /// </param>
        /// <exception cref="T:System.Security.SecurityException">The caller does not have the required permission. 
        /// </exception>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue(portNameKey, Port);
            info.AddValue(baudRateKey, BaudRate);
            info.AddValue(infoKey, Info);
            info.AddValue(invertMisoKey, InvertMiso);
            info.AddValue(invertMosiKey, InvertMosi);
            info.AddValue(invertSckKey, InvertSck);
            info.AddValue(invertResetKey, InvertReset);
            info.AddValue(outputPinsKey, OutputPins);
        }

        #endregion

        #region Nested Types

        [TypeConverter(typeof(EnumTypeConverter))]
        public enum OutputPinMapping
        {
            [Description("RTS\u2192SCK, DTR\u2192RESET")]
            RtsSck_DtrReset,

            [Description("RTS\u2192RESET, DTR\u2192SCK")]
            RtsReset_DtrSck
        }

        #endregion
    }
}
